/*
 * @Author: liuzelin
 * @Date: 2023-09-09 11:46:07
 * @LastEditors: liuzelin
 * @LastEditTime: 2024-05-13 07:28:01
 * @Description: file content
 */
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <filesystem>

#include "endpoint.hpp"
#include "json/single_include/nlohmann/json.hpp"

using namespace Panda;
using namespace nlohmann;

bool isClient = true;
Thread* main_thread = nullptr;

#define Tag (isClient ? "[Client]" : "[Server]")
#define LOGTag LOG(Debug) << Tag

uint32_t sid_file_config = 10;
uint32_t sid_file_bin = 11;
SessionParam param;

struct FileConfig {
  uint64_t size = 0;
  std::string name = "";
  std::string md5 = "";
  std::string ToJson() {
    json root = {
        {"name", this->name}, {"size", this->size}, {"md5", this->md5}};
    return root.dump();
  }
  static FileConfig* Parse(std::string& str) {
    try {
      json root = json::parse(str);
      FileConfig* ret = new FileConfig();
      ret->name = root["name"].get<std::string>();
      ret->size = root["size"].get<int>();
      ret->md5 = root["md5"].get<std::string>();
      return ret;
    } catch (const std::exception& e) {
      LOG(Error) << Tag << " Json Parse Failed " << e.what();
      return nullptr;
    }
  }
};

class EndPointListenerImpl : public Panda::EndpointInterface::Listener {
 public:
  EndPointListenerImpl() = default;
  void SetEndPoint(Panda::EndpointInterface* end_point) {
    this->end_point = end_point;
  }

  void OnSend(EndpointInterface* transport, uint8_t* data,
              size_t len) override {}
  void OnConnect(EndpointInterface* transport, const char* remote_ip,
                 uint16_t remote_port) override {
    LOGTag << " OnConnect to Remote:" << remote_ip << ":" << remote_port;
    if (fp && config) {
      std::string str_file_config = config->ToJson();
      param.sid = sid_file_config;
      size_t ret = this->end_point->Send((uint8_t*)str_file_config.c_str(),
          str_file_config.length(),
          param);
      if ( ret <= 0) {
        LOG(Error) << Tag
                   << " Send File Config Failed: " << config->name.c_str();
        exit(-3);
      }
    }
  }
  void OnDisConnect(EndpointInterface* transport) override {
    LOGTag << " OnDisConnect";
  }
  void OnSessionConnect(EndpointInterface* transport,
                        const SessionParam& param) override {
    LOGTag << " OnSessionConnect Sid:" << param.sid;
  }
  void SendFile(std::string file) {
    config = new FileConfig();
    size_t pos = file.find_last_of("/\\");
    if (pos != std::string::npos) {
      config->name = file.substr(pos + 1);
    } else {
      config->name = file;
    }
    config->md5 = "000000";
    {
      std::ifstream tfp(file, std::ifstream::ate | std::ifstream::binary);
      config->size = tfp.tellg();
      tfp.close();
    }
    fp = fopen(file.c_str(), "r+");
    if (fp == nullptr) {
      LOG(Error) << Tag << " Open File Failed: " << file.c_str();
      exit(-2);
    }
  }

  void OnRecv(EndpointInterface* transport, uint8_t* data, size_t len,
              uint32_t sid) override {
    if (isClient == false) {
      if (sid == sid_file_config) {
        std::string str((const char*)data, len);
        config = FileConfig::Parse(str);
        if (config) {
          std::stringstream ss;
          {
              namespace fs = std::filesystem; 
              fs::path dirPath = "./TempRecv";
              if (!fs::exists(dirPath)) {
                  if (!fs::create_directory(dirPath)){
                      LOG(Error) << "Failed to create directory.";
                      exit(-3);
                  }
              }
          }

          ss << "./TempRecv/" << UUidGenerator::ShortUUid(8).c_str() << "-"
             << config->name.c_str();
          fp = fopen(ss.str().c_str(), "w+");
          std::string res;
          if (fp) {
            json resJson = {{"code", 0}};
            res = resJson.dump();
          } else {
            json resJson = {{"code", -2}};
            res = resJson.dump();
          }
          param.sid = sid;
          end_point->Send((uint8_t*)res.c_str(), res.length(), param);
          LOGTag << " OnRecv File Config Success:" << str.c_str()
                 << " res:" << res;
        } else {
          json resJson = {"code", -1};
          std::string res = resJson.dump();
          param.sid = sid;
          end_point->Send((uint8_t*)res.c_str(), res.length(), param);
          LOGTag << " OnRecv File Config Failed:" << str.c_str();
        }
      } else if (sid == sid_file_bin) {
        PandaCheck(fp) << "Can`t Recv Data Before Recv Configure";
        fwrite(data, 1, len, fp);
      }
    } else {
      if (sid == sid_file_config) {
        try {
          std::string str((const char*)data, len);
          LOGTag << " Client Parse " << str.c_str();
          json root = json::parse(str);
          if (root["code"].get<int>() == 0) {
            StartSendFile();
          } else {
            LOG(Error) << Tag << " Remote Error Res " << str.c_str();
            exit(-1);
          }
        } catch (const std::exception& e) {
          LOG(Error) << Tag << " Json Parse Failed " << e.what();
          return;
        }
      }
    }
  }

 private:
  void StartSendFile() {
    PandaCheck(fp);
    while (1) {
      if (buffer_len == 0) buffer_len = fread(buffer, 1, 5000, fp);
      param.sid = sid_file_bin;
      size_t ret = this->end_point->Send((uint8_t*)buffer, buffer_len, param);
      if (ret < 0) {
        break;
      }
    }
    main_thread->PostDelayTask(
        PandaFromHere, [this]() { this->StartSendFile(); }, 100);
  }
  char buffer[5000] = {0};
  size_t buffer_len = 0;
  Panda::EndpointInterface* end_point = nullptr;
  FileConfig* config = nullptr;
  FILE* fp = nullptr;
};

int main(int argc, const char** argv) {
  ArgHandler argHandler;
  argHandler.SetOption<bool>("-s", "Server")
      .SetOption<bool>("-c", "Client Default")
      .SetOption<std::string>("-b", "Bind Address")
      .SetOption<std::string>("-f", "File Name")
      .SetOption<std::string>("-r", "Server Address");

  if (argHandler.Parse(argc, argv) == false) {
    return -1;
  }

  if (argHandler.GetValue<bool>("-s", false)) {
    isClient = false;
  }
  std::string server_address;
  std::string bind_address;

  std::string file;

  if (isClient) {
    server_address = argHandler.GetValue<std::string>("-r");
    if (server_address == "") {
      LOG(Debug) << "Client Must Set Server Address";
      LOG(Info) << "Args Error:\r\n" << argHandler.ToString();
      exit(-3);
    }

    file = argHandler.GetValue<std::string>("-f");
    if (file == "") {
      LOG(Debug) << "Client Must Set File";
      LOG(Info) << "Args Error:\r\n" << argHandler.ToString();
      exit(-3);
    }
  } else {
    bind_address = argHandler.GetValue<std::string>("-b");
    if (bind_address == "") {
      LOG(Debug) << "Server Must Set Bind Address";
      LOG(Info) << "Args Error:\r\n" << argHandler.ToString();
      exit(-3);
    }
  }

  EndPointListenerImpl impl;
  EndpointConfigure configure;
  configure.listener = &impl;
  Panda::EndpointInterface* end_point =
      Panda::EndpointInterface::CreateTransport(configure);
  impl.SetEndPoint(end_point);

  if (bind_address != "") {
    SocketAddress address(bind_address.c_str());
    end_point->Bind(address.Ip(), address.Port());
  } else {
    SocketAddress address(server_address.c_str());
    end_point->Connect(address.Ip(), address.Port());
  }

  if (file != "") {
    impl.SendFile(file);
  }
  main_thread = ThreadManager::Instance()->Current();

  LOG(Debug) << "PandaTransport Hello World";
  main_thread->Start();
  return 0;
}